using System;
using System.Globalization;
using System.Text;
using NFreeMarker.Core;

namespace NFreeMarker.Template.Utility
{
    /**
     *  Some text related utilities.
     *
     *  @version $Id: StringUtil.java,v 1.48 2005/06/01 22:39:08 ddekany Exp $
     */

    public class StringUtil
    {
        //private static final char[] ESCAPES = createEscapes();

        /*
         *  For better performance most methods are folded down. Don't you scream... :)
         */
        /**
         *  HTML encoding (does not convert line breaks).
         *  Replaces all '&gt;' '&lt;' '&amp;' and '"' with entity reference
         */
        public static string HtmlEnc(string s)
        {
            return XmlEncNA(s);
        }
        /**
         *  XML Encoding.
         *  Replaces all '&gt;' '&lt;' '&amp;', "'" and '"' with entity reference
         */
        public static string XmlEnc(string s)
        {
            return XmlOrXHtmlEnc(s, "&apos;");
        }
        /**
         *  XHTML Encoding.
         *  Replaces all '&gt;' '&lt;' '&amp;', "'" and '"' with entity reference
         *  suitable for XHTML decoding in common user agents (including legacy
         *  user agents, which do not decode "&apos;" to "'", so "&#39;" is used
         *  instead [see http://www.w3.org/TR/xhtml1/#C_16])
         */
        public static string XHtmlEnc(string s)
        {
            return XmlOrXHtmlEnc(s, "&#39;");
        }

        private static string XmlOrXHtmlEnc(string s, string aposReplacement)
        {
            int ln = s.Length;
            for (int i = 0; i < ln; i++)
            {
                char c = s[i];
                if (c == '<' || c == '>' || c == '&' || c == '"' || c == '\'')
                {
                    var b = new StringBuilder(s.Substring(0, i));
                    switch (c)
                    {
                        case '<': b.Append("&lt;"); break;
                        case '>': b.Append("&gt;"); break;
                        case '&': b.Append("&amp;"); break;
                        case '"': b.Append("&quot;"); break;
                        case '\'': b.Append(aposReplacement); break;
                    }
                    i++;
                    int next = i;
                    while (i < ln)
                    {
                        c = s[i];
                        if (c == '<' || c == '>' || c == '&' || c == '"' || c == '\'')
                        {
                            b.Append(s.Substring(next, i - next));
                            switch (c)
                            {
                                case '<': b.Append("&lt;"); break;
                                case '>': b.Append("&gt;"); break;
                                case '&': b.Append("&amp;"); break;
                                case '"': b.Append("&quot;"); break;
                                case '\'': b.Append(aposReplacement); break;
                            }
                            next = i + 1;
                        }
                        i++;
                    }
                    if (next < ln) b.Append(s.Substring(next));
                    s = b.ToString();
                    break;
                } // if c ==
            } // for
            return s;
        }

        /**
         *  XML encoding without replacing apostrophes.
         *  @see #XMLEnc(string)
         */
        public static string XmlEncNA(string s)
        {
            int ln = s.Length;
            for (int i = 0; i < ln; i++)
            {
                char c = s[i];
                if (c == '<' || c == '>' || c == '&' || c == '"')
                {
                    var b = new StringBuilder(s.Substring(0, i));
                    switch (c)
                    {
                        case '<': b.Append("&lt;"); break;
                        case '>': b.Append("&gt;"); break;
                        case '&': b.Append("&amp;"); break;
                        case '"': b.Append("&quot;"); break;
                    }
                    i++;
                    int next = i;
                    while (i < ln)
                    {
                        c = s[i];
                        if (c == '<' || c == '>' || c == '&' || c == '"')
                        {
                            b.Append(s.Substring(next, i - next));
                            switch (c)
                            {
                                case '<': b.Append("&lt;"); break;
                                case '>': b.Append("&gt;"); break;
                                case '&': b.Append("&amp;"); break;
                                case '"': b.Append("&quot;"); break;
                            }
                            next = i + 1;
                        }
                        i++;
                    }
                    if (next < ln) b.Append(s.Substring(next));
                    s = b.ToString();
                    break;
                } // if c ==
            } // for
            return s;
        }

        ///**
        // *  XML encoding for attributes valies quoted with <tt>"</tt> (not with <tt>'</tt>!).
        // *  Also can be used for HTML attributes that are quoted with <tt>"</tt>.
        // *  @see #XMLEnc(string)
        // */
        //public static string XMLEncQAttr(string s) {
        //    int ln = s.length();
        //    for (int i = 0; i < ln; i++) {
        //        char c = s.charAt(i);
        //        if (c == '<' || c == '&' || c == '"') {
        //            StringBuffer b =
        //                    new StringBuffer(s.substring(0, i));
        //            switch (c) {
        //                case '<': b.append("&lt;"); break;
        //                case '&': b.append("&amp;"); break;
        //                case '"': b.append("&quot;"); break;
        //            }
        //            i++;
        //            int next = i;
        //            while (i < ln) {
        //                c = s.charAt(i);
        //                if (c == '<' || c == '&' || c == '"') {
        //                    b.append(s.substring(next, i));
        //                    switch (c) {
        //                        case '<': b.append("&lt;"); break;
        //                        case '&': b.append("&amp;"); break;
        //                        case '"': b.append("&quot;"); break;
        //                    }
        //                    next = i + 1;
        //                }
        //                i++;
        //            }
        //            if (next < ln) {
        //                b.append(s.substring(next));
        //            }
        //            s = b.toString();
        //            break;
        //        } // if c ==
        //    } // for
        //    return s;
        //}
        ///**
        // *  XML encoding without replacing apostrophes and quotation marks and
        // *  greater-thans (except in {@code ]]>}).
        // *  @see #XMLEnc(string)
        // */
        //public static string XMLEncNQG(string s) {
        //    int ln = s.length();
        //    for (int i = 0; i < ln; i++) {
        //        char c = s.charAt(i);
        //        if (c == '<'
        //                || (c == '>' && i > 1
        //                        && s.charAt(i - 1) == ']'
        //                        && s.charAt(i - 2) == ']')
        //                || c == '&') {
        //            StringBuffer b =
        //                    new StringBuffer(s.substring(0, i));
        //            switch (c) {
        //                case '<': b.append("&lt;"); break;
        //                case '>': b.append("&gt;"); break;
        //                case '&': b.append("&amp;"); break;
        //                default: throw new RuntimeException("Bug: unexpected char");
        //            }
        //            i++;
        //            int next = i;
        //            while (i < ln) {
        //                c = s.charAt(i);
        //                if (c == '<'
        //                        || (c == '>' && i > 1
        //                                && s.charAt(i - 1) == ']'
        //                                && s.charAt(i - 2) == ']')
        //                        || c == '&') {
        //                    b.append(s.substring(next, i));
        //                    switch (c) {
        //                        case '<': b.append("&lt;"); break;
        //                        case '>': b.append("&gt;"); break;
        //                        case '&': b.append("&amp;"); break;
        //                        default: throw new RuntimeException("Bug: unexpected char");
        //                    }
        //                    next = i + 1;
        //                }
        //                i++;
        //            }
        //            if (next < ln) {
        //                b.append(s.substring(next));
        //            }
        //            s = b.toString();
        //            break;
        //        } // if c ==
        //    } // for
        //    return s;
        //}

        /**
         *  Rich Text Format encoding (does not replace line breaks).
         *  Escapes all '\' '{' '}' and '"'
         */
        public static string RtfEnc(string s)
        {
            int ln = s.Length;
            for (int i = 0; i < ln; i++)
            {
                char c = s[i];
                if (c == '\\' || c == '{' || c == '}')
                {
                    var b = new StringBuilder(s.Substring(0, i));
                    switch (c)
                    {
                        case '\\': b.Append("\\\\"); break;
                        case '{': b.Append("\\{"); break;
                        case '}': b.Append("\\}"); break;
                    }
                    i++;
                    int next = i;
                    while (i < ln)
                    {
                        c = s[i];
                        if (c == '\\' || c == '{' || c == '}')
                        {
                            b.Append(s.Substring(next, i - next));
                            switch (c)
                            {
                                case '\\': b.Append("\\\\"); break;
                                case '{': b.Append("\\{"); break;
                                case '}': b.Append("\\}"); break;
                            }
                            next = i + 1;
                        }
                        i++;
                    }
                    if (next < ln) b.Append(s.Substring(next));
                    s = b.ToString();
                    break;
                } // if c ==
            } // for
            return s;
        }

        /**
         * URL encoding (like%20this).
         */
        public static string UrlEnc(string s, Encoding charset)
        {
        //    int ln = s.length();
        //    int i;
        //    for (i = 0; i < ln; i++) {
        //        char c = s.charAt(i);
        //        if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
        //                || c >= '0' && c <= '9'
        //                || c == '_' || c == '-' || c == '.' || c == '!' || c == '~'
        //                || c >= '\'' && c <= '*')) {
        //            break;
        //        }
        //    }
        //    if (i == ln) {
        //        // Nothing to escape
        //        return s;
        //    }
        //    StringBuffer b = new StringBuffer(ln + ln / 3 + 2);
        //    b.append(s.substring(0, i));
        //    int encstart = i;
        //    for (i++; i < ln; i++) {
        //        char c = s.charAt(i);
        //        if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
        //                || c >= '0' && c <= '9'
        //                || c == '_' || c == '-' || c == '.' || c == '!' || c == '~'
        //                || c >= '\'' && c <= '*') {
        //            if (encstart != -1) {
        //                byte[] o = s.substring(encstart, i).getBytes(charset);
        //                for (int j = 0; j < o.length; j++) {
        //                    b.append('%');
        //                    byte bc = o[j];
        //                    int c1 = bc & 0x0F;
        //                    int c2 = (bc >> 4) & 0x0F;
        //                    b.append((char) (c2 < 10 ? c2 + '0' : c2 - 10 + 'A'));
        //                    b.append((char) (c1 < 10 ? c1 + '0' : c1 - 10 + 'A'));
        //                }
        //                encstart = -1;
        //            }
        //            b.append(c);
        //        } else {
        //            if (encstart == -1) {
        //                encstart = i;
        //            }
        //        }
        //    }
        //    if (encstart != -1) {
        //        byte[] o = s.substring(encstart, i).getBytes(charset);
        //        for (int j = 0; j < o.length; j++) {
        //            b.append('%');
        //            byte bc = o[j];
        //            int c1 = bc & 0x0F;
        //            int c2 = (bc >> 4) & 0x0F;
        //            b.append((char) (c2 < 10 ? c2 + '0' : c2 - 10 + 'A'));
        //            b.append((char) (c1 < 10 ? c1 + '0' : c1 - 10 + 'A'));
        //        }
        //    }
        //    return b.toString();

            throw new NotImplementedException();
        }

            //private static char[] createEscapes()
        //{
        //    char[] escapes = new char['\\' + 1];
        //    for(int i = 0; i < 32; ++i)
        //    {
        //        escapes[i] = 1;
        //    }
        //    escapes['\\'] = '\\';
        //    escapes['\''] = '\'';
        //    escapes['"'] = '"';
        //    escapes['<'] = 'l';
        //    escapes['>'] = 'g';
        //    escapes['&'] = 'a';
        //    escapes['\b'] = 'b';
        //    escapes['\t'] = 't';
        //    escapes['\n'] = 'n';
        //    escapes['\f'] = 'f';
        //    escapes['\r'] = 'r';
        //    escapes['$'] = '$';
        //    return escapes;
        //}
        //public static string FTLStringLiteralEnc(string s)
        //{
        //    StringBuffer buf = null;
        //    int l = s.length();
        //    int el = ESCAPES.length;
        //    for(int i = 0; i < l; i++)
        //    {
        //        char c = s.charAt(i);
        //        if(c < el)
        //        {
        //            char escape = ESCAPES[c];
        //            switch(escape)
        //            {
        //                case 0:
        //                {
        //                    if (buf != null) {
        //                        buf.append(c);
        //                    }
        //                    break;
        //                }
        //                case 1:
        //                {
        //                    if (buf == null) {
        //                        buf = new StringBuffer(s.length() + 3);
        //                        buf.append(s.substring(0, i));
        //                    }
        //                    // hex encoding for characters below 0x20
        //                    // that have no other escape representation
        //                    buf.append("\\x00");
        //                    int c2 = (c >> 4) & 0x0F;
        //                    c = (char) (c & 0x0F);
        //                    buf.append((char) (c2 < 10 ? c2 + '0' : c2 - 10 + 'A'));
        //                    buf.append((char) (c < 10 ? c + '0' : c - 10 + 'A'));
        //                    break;
        //                }
        //                default:
        //                {
        //                    if (buf == null) {
        //                        buf = new StringBuffer(s.length() + 2);
        //                        buf.append(s.substring(0, i));
        //                    }
        //                    buf.append('\\');
        //                    buf.append(escape);
        //                }
        //            }
        //        } else {
        //            if (buf != null) {
        //                buf.append(c);
        //            }
        //        }
        //    }
        //    return buf == null ? s : buf.toString();
        //}
        /**
         * FTL string literal decoding.
         *
         * \\, \", \', \n, \t, \r, \b and \f will be replaced according to
         * Java rules. In additional, it knows \g, \l, \a and \{ which are
         * replaced with &lt;, >, &amp; and { respectively.
         * \x works as hexadecimal character code escape. The character
         * codes are interpreted according to UCS basic plane (Unicode).
         * "f\x006Fo", "f\x06Fo" and "f\x6Fo" will be "foo".
         * "f\x006F123" will be "foo123" as the maximum number of digits is 4.
         *
         * All other \X (where X is any character not mentioned above or End-of-string)
         * will cause a ParseException.
         *
         * @param s string literal <em>without</em> the surrounding quotation marks
         * @return string with all escape sequences resolved
         * @throws ParseException if there string contains illegal escapes
         */
        public static string FtlStringLiteralDec(string s)
        {
            int idx = s.IndexOf('\\');
            if (idx == -1)
            {
                return s;
            }

            int lidx = s.Length - 1;
            int bidx = 0;
            var buf = new StringBuilder(lidx);
            do
            {
                buf.Append(s.Substring(bidx, idx - bidx));
                if (idx >= lidx)
                {
                    throw new ParseException("The last character of string literal is backslash", 0, 0);
                }
                char c = s[idx + 1];
                switch (c)
                {
                    case '"':
                        buf.Append('"');
                        bidx = idx + 2;
                        break;
                    case '\'':
                        buf.Append('\'');
                        bidx = idx + 2;
                        break;
                    case '\\':
                        buf.Append('\\');
                        bidx = idx + 2;
                        break;
                    case 'n':
                        buf.Append('\n');
                        bidx = idx + 2;
                        break;
                    case 'r':
                        buf.Append('\r');
                        bidx = idx + 2;
                        break;
                    case 't':
                        buf.Append('\t');
                        bidx = idx + 2;
                        break;
                    case 'f':
                        buf.Append('\f');
                        bidx = idx + 2;
                        break;
                    case 'b':
                        buf.Append('\b');
                        bidx = idx + 2;
                        break;
                    case 'g':
                        buf.Append('>');
                        bidx = idx + 2;
                        break;
                    case 'l':
                        buf.Append('<');
                        bidx = idx + 2;
                        break;
                    case 'a':
                        buf.Append('&');
                        bidx = idx + 2;
                        break;
                    case '{':
                        buf.Append('{');
                        bidx = idx + 2;
                        break;
                    case 'x':
                        {
                            idx += 2;
                            int x = idx;
                            int y = 0;
                            int z = lidx > idx + 3 ? idx + 3 : lidx;
                            while (idx <= z)
                            {
                                char b = s[idx];
                                if (b >= '0' && b <= '9')
                                {
                                    y <<= 4;
                                    y += b - '0';
                                }
                                else if (b >= 'a' && b <= 'f')
                                {
                                    y <<= 4;
                                    y += b - 'a' + 10;
                                }
                                else if (b >= 'A' && b <= 'F')
                                {
                                    y <<= 4;
                                    y += b - 'A' + 10;
                                }
                                else
                                {
                                    break;
                                }
                                idx++;
                            }
                            if (x < idx)
                            {
                                buf.Append((char) y);
                            }
                            else
                            {
                                throw new ParseException("Invalid \\x escape in a string literal", 0, 0);
                            }
                            bidx = idx;
                            break;
                        }
                    default:
                        throw new ParseException("Invalid escape sequence (\\" + c + ") in a string literal", 0, 0);
                }
                idx = s.IndexOf('\\', bidx);
            } while (idx != -1);
            buf.Append(s.Substring(bidx));

            return buf.ToString();
        }

        public static CultureInfo DeduceLocale(string input)
        {
            if (input[0] == '"') input = input.Substring(1, input.Length - 2);
            string[] st = input.Split(",_ ".ToCharArray());
            string localeStr = string.Empty;
            if (st.Length > 0)
            {
                localeStr += st[0];
            }
            if (st.Length > 1)
            {
                localeStr += ("-" + st[1]);
            }
            if (st.Length > 2)
            {
                localeStr += ("-" + st[2]);
            }
            try
            {
                return new CultureInfo(localeStr);
            }
            catch (ArgumentException)
            {
                return CultureInfo.CurrentCulture;
            }
        }

        public static string Capitalize(string s)
        {
            var builder = new StringBuilder();

            int prevIndex = 0;
            int nextIndex;

            do
            {
                nextIndex = s.IndexOfAny(" \t\r\n".ToCharArray(), prevIndex);

                string tok = (nextIndex != -1)
                    ? s.Substring(prevIndex, nextIndex - prevIndex + 1) 
                    : s.Substring(prevIndex);

                builder.Append(tok.Substring(0, 1).ToUpper());
                builder.Append(tok.Substring(1).ToLower());

                prevIndex = nextIndex + 1;
            } while (nextIndex != -1);

            return builder.ToString();
        }

        public static bool GetYesNo(string s)
        {
            if (s.StartsWith("\""))
            {
                s = s.Substring(1, s.Length - 2);
            }
            if (s.Equals("n", StringComparison.OrdinalIgnoreCase) ||
                s.Equals("no", StringComparison.OrdinalIgnoreCase) ||
                s.Equals("f", StringComparison.OrdinalIgnoreCase) ||
                s.Equals("false", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
            if (s.Equals("y", StringComparison.OrdinalIgnoreCase) ||
                s.Equals("yes", StringComparison.OrdinalIgnoreCase) ||
                s.Equals("t", StringComparison.OrdinalIgnoreCase) ||
                s.Equals("true", StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
            throw new ArgumentException("Illegal boolean value: " + s);
        }

        /**
         * Splits a string at the specified character.
         */
        public static string[] Split(string s, char c) {
            int ln = s.Length;
            int i = 0;
            int cnt = 1;
            while ((i = s.IndexOf(c, i)) != -1) {
                cnt++;
                i++;
            }
            var res = new string[cnt];
            i = 0;
            int b = 0;
            while (b <= ln) {
                int e = s.IndexOf(c, b);
                if (e == - 1) e = ln;
                res[i++] = s.Substring(b, e - b);
                b = e + 1;
            }
            return res;
        }

        /**
         * Splits a string at the specified string.
         */
        public static string[] Split(string s, string sep, bool caseInsensitive) {
            string splitString = caseInsensitive ? sep.ToLower() : sep;
            string input = caseInsensitive ? s.ToLower() : s;
            int ln = s.Length;
            int sln = sep.Length;
            if (sln == 0) throw new ArgumentException(
                    "The separator string has 0 length");
            int i = 0;
            int cnt = 1;
            while ((i = input.IndexOf(splitString, i)) != -1) {
                cnt++;
                i += sln;
            }
            var res = new string[cnt];
            i = 0;
            int b = 0;
            while (b <= ln) {
                int e = input.IndexOf(splitString, b);
                if (e == -1) e = ln;
                res[i++] = s.Substring(b, e - b);
                b = e + sln;
            }
            return res;
        }

        /**
         * Replaces all occurrences of a sub-string in a string.
         * @param text The string where it will replace <code>oldsub</code> with
         *     <code>newsub</code>.
         * @return string The string after the replacements.
         */
        public static string Replace(
            string text,
            string oldsub,
            string newsub,
            bool caseInsensitive,
            bool firstOnly)
        {
            StringBuilder buf;
            int tln;
            int oln = oldsub.Length;
            if (oln == 0)
            {
                int nln = newsub.Length;
                if (nln == 0)
                {
                    return text;
                }
                else
                {
                    if (firstOnly)
                    {
                        return newsub + text;
                    }
                    else
                    {
                        tln = text.Length;
                        buf = new StringBuilder(tln + (tln + 1) * nln);
                        buf.Append(newsub);
                        for (int i = 0; i < tln; i++)
                        {
                            buf.Append(text[i]);
                            buf.Append(newsub);
                        }
                        return buf.ToString();
                    }
                }
            }
            else
            {
                oldsub = caseInsensitive ? oldsub.ToLower() : oldsub;
                string input = caseInsensitive ? text.ToLower() : text;
                int e = input.IndexOf(oldsub);
                if (e == -1)
                {
                    return text;
                }
                int b = 0;
                tln = text.Length;
                buf = new StringBuilder(
                        tln + Math.Max(newsub.Length - oln, 0) * 3);
                do
                {
                    buf.Append(text.Substring(b, e - b));
                    buf.Append(newsub);
                    b = e + oln;
                    e = input.IndexOf(oldsub, b);
                } while (e != -1 && !firstOnly);
                buf.Append(text.Substring(b));
                return buf.ToString();
            }
        }

        /// <summary>
        /// Removes the line-break from the end of the string.
        /// </summary>
        /// <param name="s">The string whose ending line-break shall be removed.</param>
        /// <returns>The string with the ending line-break removed.</returns>
        public static string Chomp(string s)
        {
            if (s.EndsWith("\r\n"))
            {
                return s.Substring(0, s.Length - 2);
            }
            if (s.EndsWith("\r") || s.EndsWith("\n"))
            {
                return s.Substring(0, s.Length - 1);
            }
            return s;
        }
        /**
         * Quotes string as Java Language string literal.
         * Returns string <code>"null"</code> if <code>s</code>
         * is <code>null</code>.
         */
        public static string JQuote(string s)
        {
            if (s == null)
            {
                return "null";
            }
            int ln = s.Length;
            var b = new StringBuilder(ln + 4);
            b.Append('"');
            foreach (char c in s)
            {
                switch (c)
                {
                    case '"':
                        b.Append("\\\"");
                        break;
                    case '\\':
                        b.Append("\\\\");
                        break;
                    default:
                        if (c < 0x20)
                        {
                            switch (c)
                            {
                                case '\n':
                                    b.Append("\\n");
                                    break;
                                case '\r':
                                    b.Append("\\r");
                                    break;
                                case '\f':
                                    b.Append("\\f");
                                    break;
                                case '\b':
                                    b.Append("\\b");
                                    break;
                                case '\t':
                                    b.Append("\\t");
                                    break;
                                default:
                                    {
                                        b.Append("\\u00");
                                        int x = c/0x10;
                                        b.Append((char) (x < 0xA ? x + '0' : x - 0xA + 'A'));
                                        x = c & 0xF;
                                        b.Append((char) (x < 0xA ? x + '0' : x - 0xA + 'A'));
                                    }
                                    break;
                            }
                        }
                        else
                        {
                            b.Append(c);
                        }
                        break;
                }
            } // for each characters
            b.Append('"');
            return b.ToString();
        }

        /**
         * Escapes the <code>string</code> with the escaping rules of Java language
         * string literals, so it is safe to insert the value into a string literal.
         * The resulting string will not be quoted.
         * 
         * <p>In additional, all characters under UCS code point 0x20, that has no
         * dedicated escape sequence in Java language, will be replaced with UNICODE
         * escape (<tt>\<!-- -->u<i>XXXX</i></tt>).
         * 
         * @see #jQuote(string)
         */
        public static string JavaStringEnc(string s)
        {
            int ln = s.Length;
            for (int i = 0; i < ln; i++)
            {
                char c = s[i];
                if (c == '"' || c == '\\' || c < 0x20)
                {
                    var b = new StringBuilder(ln + 4);
                    b.Append(s.Substring(0, i));
                    while (true)
                    {
                        if (c == '"')
                        {
                            b.Append("\\\"");
                        }
                        else if (c == '\\')
                        {
                            b.Append("\\\\");
                        }
                        else if (c < 0x20)
                        {
                            if (c == '\n')
                            {
                                b.Append("\\n");
                            }
                            else if (c == '\r')
                            {
                                b.Append("\\r");
                            }
                            else if (c == '\f')
                            {
                                b.Append("\\f");
                            }
                            else if (c == '\b')
                            {
                                b.Append("\\b");
                            }
                            else if (c == '\t')
                            {
                                b.Append("\\t");
                            }
                            else
                            {
                                b.Append("\\u00");
                                int x = c / 0x10;
                                b.Append((char)
                                        (x < 0xA ? x + '0' : x - 0xA + 'a'));
                                x = c & 0xF;
                                b.Append((char)
                                        (x < 0xA ? x + '0' : x - 0xA + 'a'));
                            }
                        }
                        else
                        {
                            b.Append(c);
                        }
                        i++;
                        if (i >= ln)
                        {
                            return b.ToString();
                        }
                        c = s[i];
                    }
                } // if has to be escaped
            } // for each characters
            return s;
        }

        /**
         * Escapes a <code>string</code> according the JavaScript string literal
         * escaping rules. The resulting string will not be quoted.
         * 
         * <p>It escapes both <tt>'</tt> and <tt>"</tt>.
         * In additional it escapes <tt>></tt> as <tt>\></tt> (to avoid
         * <tt>&lt;/script></tt>). Furthermore, all characters under UCS code point
         * 0x20, that has no dedicated escape sequence in JavaScript language, will
         * be replaced with hexadecimal escape (<tt>\x<i>XX</i></tt>). 
         */
        public static string JavaScriptStringEnc(string s)
        {
            int ln = s.Length;
            for (int i = 0; i < ln; i++)
            {
                char c = s[i];
                if (c == '"' || c == '\'' || c == '\\' || c == '>' || c < 0x20)
                {
                    var b = new StringBuilder(ln + 4);
                    b.Append(s.Substring(0, i));
                    while (true)
                    {
                        if (c == '"')
                        {
                            b.Append("\\\"");
                        }
                        else if (c == '\'')
                        {
                            b.Append("\\'");
                        }
                        else if (c == '\\')
                        {
                            b.Append("\\\\");
                        }
                        else if (c == '>')
                        {
                            b.Append("\\>");
                        }
                        else if (c < 0x20)
                        {
                            if (c == '\n')
                            {
                                b.Append("\\n");
                            }
                            else if (c == '\r')
                            {
                                b.Append("\\r");
                            }
                            else if (c == '\f')
                            {
                                b.Append("\\f");
                            }
                            else if (c == '\b')
                            {
                                b.Append("\\b");
                            }
                            else if (c == '\t')
                            {
                                b.Append("\\t");
                            }
                            else
                            {
                                b.Append("\\x");
                                int x = c / 0x10;
                                b.Append((char)
                                        (x < 0xA ? x + '0' : x - 0xA + 'A'));
                                x = c & 0xF;
                                b.Append((char)
                                        (x < 0xA ? x + '0' : x - 0xA + 'A'));
                            }
                        }
                        else
                        {
                            b.Append(c);
                        }
                        i++;
                        if (i >= ln)
                        {
                            return b.ToString();
                        }
                        c = s[i];
                    }
                } // if has to be escaped
            } // for each characters
            return s;
        }

        ///**
        // * Parses a name-value pair list, where the pairs are separated with comma,
        // * and the name and value is separated with colon.
        // * The keys and values can contain only letters, digits and <tt>_</tt>. They
        // * can't be quoted. White-space around the keys and values are ignored. The
        // * value can be omitted if <code>defaultValue</code> is not null. When a
        // * value is omitted, then the colon after the key must be omitted as well.
        // * The same key can't be used for multiple times.
        // * 
        // * @param s the string to parse.
        // *     For example: <code>"strong:100, soft:900"</code>.
        // * @param defaultValue the value used when the value is omitted in a
        // *     key-value pair.
        // * 
        // * @return the map that contains the name-value pairs.
        // * 
        // * @throws java.text.ParseException if the string is not a valid name-value
        // *     pair list.
        // */
        //public static Map parseNameValuePairList(string s, string defaultValue)
        //throws java.text.ParseException {
        //    Map map = new HashMap();

        //    char c = ' ';
        //    int ln = s.length();
        //    int p = 0;
        //    int keyStart;
        //    int valueStart;
        //    string key;
        //    string value;

        //    fetchLoop: while (true) {
        //        // skip ws
        //        while (p < ln) {
        //            c = s.charAt(p);
        //            if (!Character.isWhitespace(c)) {
        //                break;
        //            }
        //            p++;
        //        }
        //        if (p == ln) {
        //            break fetchLoop;
        //        }
        //        keyStart = p;

        //        // seek key end
        //        while (p < ln) {
        //            c = s.charAt(p);
        //            if (!(Character.isLetterOrDigit(c) || c == '_')) {
        //                break;
        //            }
        //            p++;
        //        }
        //        if (keyStart == p) {
        //            throw new java.text.ParseException(
        //                   "Expecting letter, digit or \"_\" "
        //                    + "here, (the first character of the key) but found "
        //                    + jQuote(string.valueOf(c))
        //                    + " at position " + p + ".",
        //                    p);
        //        }
        //        key = s.substring(keyStart, p);

        //        // skip ws
        //        while (p < ln) {
        //            c = s.charAt(p);
        //            if (!Character.isWhitespace(c)) {
        //                break;
        //            }
        //            p++;
        //        }
        //        if (p == ln) {
        //            if (defaultValue == null) {
        //                throw new java.text.ParseException(
        //                        "Expecting \":\", but reached "
        //                        + "the end of the string "
        //                        + " at position " + p + ".",
        //                        p);
        //            }
        //            value = defaultValue;
        //        } else if (c != ':') {
        //            if (defaultValue == null || c != ',') {
        //                throw new java.text.ParseException(
        //                        "Expecting \":\" here, but found "
        //                        + jQuote(string.valueOf(c))
        //                        + " at position " + p + ".",
        //                        p);
        //            }

        //            // skip ","
        //            p++;

        //            value = defaultValue;
        //        } else {
        //            // skip ":"
        //            p++;

        //            // skip ws
        //            while (p < ln) {
        //                c = s.charAt(p);
        //                if (!Character.isWhitespace(c)) {
        //                    break;
        //                }
        //                p++;
        //            }
        //            if (p == ln) {
        //                throw new java.text.ParseException(
        //                        "Expecting the value of the key "
        //                        + "here, but reached the end of the string "
        //                        + " at position " + p + ".",
        //                        p);
        //            }
        //            valueStart = p;

        //            // seek value end
        //            while (p < ln) {
        //                c = s.charAt(p);
        //                if (!(Character.isLetterOrDigit(c) || c == '_')) {
        //                    break;
        //                }
        //                p++;
        //            }
        //            if (valueStart == p) {
        //                throw new java.text.ParseException(
        //                        "Expecting letter, digit or \"_\" "
        //                        + "here, (the first character of the value) "
        //                        + "but found "
        //                        + jQuote(string.valueOf(c))
        //                        + " at position " + p + ".",
        //                        p);
        //            }
        //            value = s.substring(valueStart, p);

        //            // skip ws
        //            while (p < ln) {
        //                c = s.charAt(p);
        //                if (!Character.isWhitespace(c)) {
        //                    break;
        //                }
        //                p++;
        //            }

        //            // skip ","
        //            if (p < ln) {
        //                if (c != ',') {
        //                    throw new java.text.ParseException(
        //                            "Excpecting \",\" or the end "
        //                            + "of the string here, but found "
        //                            + jQuote(string.valueOf(c))
        //                            + " at position " + p + ".",
        //                            p);
        //                } else {
        //                    p++;
        //                }
        //            }
        //        }

        //        // store the key-value pair
        //        if (map.put(key, value) != null) {
        //            throw new java.text.ParseException(
        //                    "Dublicated key: "
        //                    + jQuote(key), keyStart);
        //        }
        //    }

        //    return map;
        //}

        ///**
        // * @return whether the name is a valid XML tagname.
        // * (This routine might only be 99% accurate. Should maybe REVISIT) 
        // */
        //static public boolean isXMLID(string name) {
        //    for (int i=0; i<name.length(); i++) {
        //        char c = name.charAt(i);
        //        if (i==0) {
        //            if (c== '-' || c=='.' || Character.isDigit(c))
        //                return false;
        //        }
        //        if (!Character.isLetterOrDigit(c) && c != ':' && c != '_' && c != '-' && c!='.') {
        //            return false;
        //        }
        //    }
        //    return true;
        //}

        /**
         * @return whether the qname matches the combination of nodeName, nsURI, and environment prefix settings. 
         */
        public static bool MatchesName(string qname, string nodeName, string nsUri, Core.Environment env)
        {
            string defaultNs = env.GetDefaultNs();
            if ((defaultNs != null) && defaultNs == nsUri)
            {
                return qname == nodeName ||
                       qname == Template.DefaultNamespacePrefix + ":" + nodeName;
            }
            if (string.IsNullOrEmpty(nsUri))
            {
                if (defaultNs != null)
                {
                    return qname == Template.NoNsPrefix + ":" + nodeName;
                }
                else
                {
                    return qname == nodeName || qname == Template.NoNsPrefix + ":" + nodeName;
                }
            }
            string prefix = env.GetPrefixForNamespace(nsUri);
            if (prefix == null)
            {
                return false; // Is this the right thing here???
            }
            return qname == prefix + ":" + nodeName;
        }

        /**
         * Pads the string at the left with spaces until it reaches the desired
         * length. If the string is longer than this length, then it returns the
         * unchanged string. 
         * 
         * @param s the string that will be padded.
         * @param minLength the length to reach.
         */
        public static string LeftPad(string s, int minLength)
        {
            return LeftPad(s, minLength, ' ');
        }

        /**
         * Pads the string at the left with the specified character until it reaches
         * the desired length. If the string is longer than this length, then it
         * returns the unchanged string.
         * 
         * @param s the string that will be padded.
         * @param minLength the length to reach.
         * @param filling the filling pattern.
         */
        public static string LeftPad(string s, int minLength, char filling)
        {
            int ln = s.Length;
            if (minLength <= ln)
            {
                return s;
            }

            var res = new StringBuilder(minLength);

            int dif = minLength - ln;
            for (int i = 0; i < dif; i++)
            {
                res.Append(filling);
            }

            res.Append(s);

            return res.ToString();
        }

        /**
         * Pads the string at the left with a filling pattern until it reaches the
         * desired length. If the string is longer than this length, then it returns
         * the unchanged string. For example: <code>leftPad('ABC', 9, '1234')</code>
         * returns <code>"123412ABC"</code>.
         * 
         * @param s the string that will be padded.
         * @param minLength the length to reach.
         * @param filling the filling pattern. Must be at least 1 characters long.
         *     Can't be <code>null</code>.
         */
        public static string LeftPad(string s, int minLength, string filling)
        {
            int ln = s.Length;
            if (minLength <= ln)
            {
                return s;
            }

            var res = new StringBuilder(minLength);

            int dif = minLength - ln;
            int fln = filling.Length;
            if (fln == 0)
            {
                throw new ArgumentException(
                        "The \"filling\" argument can't be 0 length string.");
            }
            int cnt = dif / fln;
            for (int i = 0; i < cnt; i++)
            {
                res.Append(filling);
            }
            cnt = dif % fln;
            for (int i = 0; i < cnt; i++)
            {
                res.Append(filling[i]);
            }

            res.Append(s);

            return res.ToString();
        }

        /**
         * Pads the string at the right with spaces until it reaches the desired
         * length. If the string is longer than this length, then it returns the
         * unchanged string. 
         * 
         * @param s the string that will be padded.
         * @param minLength the length to reach.
         */
        public static string RightPad(string s, int minLength) {
            return RightPad(s, minLength, ' ');
        }

        /**
         * Pads the string at the right with the specified character until it
         * reaches the desired length. If the string is longer than this length,
         * then it returns the unchanged string.
         * 
         * @param s the string that will be padded.
         * @param minLength the length to reach.
         * @param filling the filling pattern.
         */
        public static string RightPad(string s, int minLength, char filling)
        {
            int ln = s.Length;
            if (minLength <= ln)
            {
                return s;
            }

            var res = new StringBuilder(minLength);

            res.Append(s);

            int dif = minLength - ln;
            for (int i = 0; i < dif; i++)
            {
                res.Append(filling);
            }

            return res.ToString();
        }

        /**
         * Pads the string at the right with a filling pattern until it reaches the
         * desired length. If the string is longer than this length, then it returns
         * the unchanged string. For example: <code>rightPad('ABC', 9, '1234')</code>
         * returns <code>"ABC412341"</code>. Note that the filling pattern is
         * started as if you overlay <code>"123412341"</code> with the left-aligned
         * <code>"ABC"</code>, so it starts with <code>"4"</code>.
         * 
         * @param s the string that will be padded.
         * @param minLength the length to reach.
         * @param filling the filling pattern. Must be at least 1 characters long.
         *     Can't be <code>null</code>.
         */
        public static string RightPad(string s, int minLength, string filling)
        {
            int ln = s.Length;
            if (minLength <= ln)
            {
                return s;
            }

            var res = new StringBuilder(minLength);

            res.Append(s);

            int dif = minLength - ln;
            int fln = filling.Length;
            if (fln == 0)
            {
                throw new ArgumentException(
                        "The \"filling\" argument can't be 0 length string.");
            }
            int start = ln % fln;
            int end = fln - start <= dif
                    ? fln
                    : start + dif;
            for (int i = start; i < end; i++)
            {
                res.Append(filling[i]);
            }
            dif -= end - start;
            int cnt = dif / fln;
            for (int i = 0; i < cnt; i++)
            {
                res.Append(filling);
            }
            cnt = dif % fln;
            for (int i = 0; i < cnt; i++)
            {
                res.Append(filling[i]);
            }

            return res.ToString();
        }
    }
}